/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package station;

import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.MulticastSocket;
import java.net.NetworkInterface;

/**
 *
 * @author safe
 */
public class Taktgeber extends Thread {
    public static final int FRAME_DAUER = 1000;
    private static final int EINE_SEKUNDE_IN_MILLIS = 1000;
    private static final int PUFFER_WEGEN_ZEITAENDERUNGEN = 100;
    private static final int VERZOEGERUNG_ERSTES_FRAME = 1000;

    private final String interfaceName;
    private final String ip;
    private final int port;
    private final MulticastSocket multicastSocket;

    public Taktgeber(String interfaceName, String ip, int port) {
        this.interfaceName = interfaceName;
        this.ip = ip;
        this.port = port;
        this.multicastSocket = initializeMulticast();
    }

    private long abrundenZurVollenSekunde(long millisBisZumNaechtenFrame) {
        return millisBisZumNaechtenFrame - (millisBisZumNaechtenFrame % EINE_SEKUNDE_IN_MILLIS);
    }

    private boolean istFrameGestartet(long synchronisierteZeit, long startZeitNaechsterFrame) {
        return synchronisierteZeit >= startZeitNaechsterFrame;
    }

    private void resetSlotsAndSynchronizeClock() {
        DataSlotBelegung.setAllToFalse();
 //       DataEmpfangeneNachrichten.setSendingCollision(false);
        DataAbweichungsZeit.adaptTimeFrameEnd();
    }

    private MulticastSocket initializeMulticast() {
        try {
            InetSocketAddress group = new InetSocketAddress(InetAddress.getByName(ip), port);
            MulticastSocket multicastsocket = new MulticastSocket(port);
            NetworkInterface networkInterface = NetworkInterface.getByName(interfaceName);
            multicastsocket.joinGroup(group, networkInterface);
            multicastsocket.setTimeToLive(1);
            multicastsocket.setLoopbackMode(false);

            return multicastsocket;
        } catch (IOException ex) {
            System.out.println(ex.getMessage());
            return null;
        }
    }

    @Override
    public void run() {
        long startZeitNaechsterFrame = System.currentTimeMillis() + DataAbweichungsZeit.getAbWeichungsZeit() + VERZOEGERUNG_ERSTES_FRAME;
        startZeitNaechsterFrame = abrundenZurVollenSekunde(startZeitNaechsterFrame);

        while (!isInterrupted()) {
            long abweichungszeit = DataAbweichungsZeit.getAbWeichungsZeit();
            long synchronisierteZeit = System.currentTimeMillis() + abweichungszeit;

            // Aktives warten kostet viel CPU-Zeit. Vielleicht hängen dadurch die Workerthreads hinterher
            if (istFrameGestartet(synchronisierteZeit, startZeitNaechsterFrame)) {
                startZeitNaechsterFrame = System.currentTimeMillis() + abweichungszeit + FRAME_DAUER;
                
                // Warum nochmal abrunden? Ihr seid ja schon auf den vollen Sekunden.
                // Das ist ja nur nötig wenn sich die Abweichungszeit ändert. Aber dann zieht ihr ja was von
                // einem Frame ab. D.h. irgendein Slot wird verkürzt. Kann das zu Fehlern führen?
                startZeitNaechsterFrame = startZeitNaechsterFrame - (startZeitNaechsterFrame % 1000);
//                if (DataEmpfangeneNachrichten.getSendingCollision()) {
//                    DataSlotBelegung.calculateNextFreeSlot();
//                }
                this.resetSlotsAndSynchronizeClock();

                Sender sender = new Sender(multicastSocket);
                sender.setPriority(MAX_PRIORITY);
                sender.start();
                try {
                    sleep(FRAME_DAUER - PUFFER_WEGEN_ZEITAENDERUNGEN);
                    // Merkwuerdige Redudanz. Ist das wirlich nötig?
                    sender.join();
                } catch (InterruptedException ex) {
                }
            }
        }
    }
}
